﻿using System;
using System.ComponentModel;
using System.Security.Permissions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls.WebParts;
using System.Xml;
using System.Xml.XPath;
using Microsoft.SharePoint.Security;

namespace Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI
{
    /// <summary>
    /// ESPSearchPaging is used to page between search results.
    /// Properties like number of pages before current, after current and the display names for the
    /// next and previouse page can be configured.
    /// 
    /// </summary>
    public class ESPSearchPaging: ESPSearchWebPart
    {
        #region Private variables
        /// <summary>
        /// <see cref="IXPathNavigable"/> (XmlDocument) containing the paging information in xml document which is used for the xslt transormation.        
        /// </summary>
        private XmlDocument resultXmlDoc;
        
        /// <summary>
        /// Currently selected page number
        /// </summary>
        private int _currentPage;

        /// <summary>
        /// Total number of pages
        /// </summary>
        private int _totalPageNum;

        /// <summary>
        /// Hits per page. 
        /// </summary>
        private int _hitsPerPage;

        #endregion

        #region WebPart Properties

        /// <summary>
        /// Maximum number of page links shown before the currently selected page.<br></br>        
        /// Not inluding the "Next" page link
        /// </summary>        
        [Category("Results Paging"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Maximum page links before current"), WebDescription(""),
        DefaultValue(5)]
        public int MaxNumberPageLinksBeforeCurrent
        {
            get;
            set;
        }
        /// <summary>
        /// Maximum number of page links shown after the currently selected page.<br></br>
        /// Not inluding the "Previous" page link
        /// </summary>
        [Category("Results Paging"), Personalizable(PersonalizationScope.Shared), 
        WebBrowsable(true), WebDisplayName("Maximum page links after current"), WebDescription(""),
        DefaultValue(4)]
        public int MaxNumberPageLinksAfterCurrent
        {
            get;
            set;
        }

        /// <summary>
        /// Text label to be shown in the UI for the Previous page.
        /// If an image shall be shown it can be done by changing the xslt.
        /// </summary>
        [Category("Results Paging"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Previous link text label"), WebDescription(""),
        DefaultValue("<Prev")]
        public string PrevTextLabel
        {
            get;
            set;
        }

        /// <summary>
        /// Text label to be shown in the UI for the Next page.
        /// If an image shall be shown it can be done by changing the xslt.
        /// </summary>
        [Category("Results Paging"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("Next link text label"), WebDescription(""),
        DefaultValue("Next>")]
        public string NextTextLabel
        {
            get;
            set;
        }

        /// <summary>
        /// Text label to be shown in the UI for the First page.
        /// If an image shall be shown it can be done by changing the xslt.
        /// </summary>
        [Category("Results Paging"), Personalizable(PersonalizationScope.Shared), WebBrowsable(true), WebDisplayName("First page link text label"), WebDescription(""),
        DefaultValue("<<First")]
        public string FirstPageTextLabel
        {
            get;
            set;
        }

        #endregion

        /// <summary>
        /// Setting some predefined values for the WebPart Properties
        /// </summary>
        public ESPSearchPaging()
        {
            // Default values in annotation didnt' work.....
            NextTextLabel = "Next>";
            PrevTextLabel = "<Prev";
            FirstPageTextLabel = "<<First";
            MaxNumberPageLinksAfterCurrent = 5;
            MaxNumberPageLinksBeforeCurrent = 4;
        }

        #region ESPSearchWebPart overrides
        /// <summary>
        /// Return the xpath navigator for the result xml object used by the xslt transformation
        /// </summary>
        /// <param name="viewPath">View path for the xpath navigator. Not used as the root is returned</param>
        /// <returns><see cref="XPathNavigator"/>XPath Navigator used for the xslt rendering</returns>
        [AspNetHostingPermission(SecurityAction.LinkDemand,Level = AspNetHostingPermissionLevel.Minimal)]
        [SharePointPermission(SecurityAction.LinkDemand, ObjectModel = true)]    
        protected override XPathNavigator GetXPathNavigator(string viewPath)
        {
            if (resultXmlDoc != null)
            {
                return resultXmlDoc.CreateNavigator();
            }
            // Empty result list
            return new XmlDocument().CreateNavigator();
        }

        /// <summary>
        /// Add the Web Part custom properties which are search specific to the <see cref="Com.FastSearch.SharePoint.WebControls.ESPSearchWebParts.UI.Client.SearchRequest"/><br/>
        /// See <see cref="ESPSearchWebPart.ConfigureSearch"/> for details.
        /// Nothing is used by this webpart. Just implementing the abstract method
        /// </summary>
        protected override void ConfigureSearch()
        {
            // Nothing to keep in request scope...
        }

        /// <summary>
        /// Specify the default XSLT file used for the result list.
        /// </summary>
        /// <returns>string containing the xslt</returns>
        public override string GetEmbeddedXsl()
        {
            return LoadXslFromAssembly("ResultPagination.xslt");
        }
       
        /// <summary>
        /// See <see cref="ESPSearchWebPart.SetSearchResult"/>
        /// </summary>
        /// <param name="SearchResult">object containing all result information. <see cref="Com.FastSearch.SharePoint.Interfaces.IESPSearchResult"/></param>       
        public override void SetSearchResult(Interfaces.IESPSearchResult SearchResult)
        {
            if (SearchResult != null)
            {
                resultXmlDoc = BuildXmlPaging(SearchRequest.Start1, SearchResult.DocCount, SearchRequest.NumberOfResultsPerPage,
                    Page.Request);
            }
        }

        #endregion

        #region XML Pagination Document Building
        /// <summary>
        /// Build the Pagination XML Document used for xslt rendering
        /// </summary>
        /// <param name="offset">Offset of the current searchg</param>
        /// <param name="doccount">Number of total documents for this search</param>
        /// <param name="hitsPerPage">Number of hits per page</param>
        /// <param name="request">HTTPRequest to build up the pagination links</param>
        /// <returns>XMLDocument describing the pagination. Used to render with xslt</returns>
        internal XmlDocument BuildXmlPaging(int offset, int doccount, int hitsPerPage,
            HttpRequest request)
        {
            _hitsPerPage = hitsPerPage;
            if (_hitsPerPage <= 0)
            {
                _hitsPerPage = 1;
            }

            NavigationHelperParameter navParameter = new NavigationHelperParameter(
                request.RawUrl,request.QueryString, request.FilePath);

            _totalPageNum = ((doccount - 1) / _hitsPerPage) + 1;

            _currentPage = (offset / _hitsPerPage) + 1;
            XmlDocument doc = new XmlDocument();
            XmlNode root = doc.AppendChild(doc.CreateElement("Pagination"));            

            XmlElement pagesNode = doc.CreateElement("Pages");

            AddFirstCellXmlNode(pagesNode, doc, navParameter);
            AddPrevCellXmlNode(pagesNode, doc, navParameter);
            AddPageBeforeCurrentXmlNode(pagesNode, doc, navParameter);
            AddCurrentXmlNode(pagesNode, doc, navParameter);
            AddPageAfterCurrentXmlNode(pagesNode, doc, navParameter);
            AddNextCellXmlNode(pagesNode, doc, navParameter);
            root.AppendChild(pagesNode);
            return doc;
        }

        /// <summary>
        /// Add the "next" page xml node to the xml document
        /// </summary>
        /// <param name="parentNode">Parent xml node where the next page shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode </param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddNextCellXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            if (_currentPage < _totalPageNum)
            {
                string url = GetRequestURL(navParameter,
                                             (_currentPage * _hitsPerPage).ToString());
                AddPageXmlNode(parentNode, doc, NextTextLabel, url, false, false, true);
            }
        }

        /// <summary>
        /// Add the "prev" page xml node to the xml document
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddPrevCellXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            if (_currentPage > 1)
            {
                string url = GetRequestURL(navParameter,
                                           ((_currentPage - 2) * _hitsPerPage).ToString());
                AddPageXmlNode(parentNode, doc, PrevTextLabel, url, false, true, false);
            }
        }

        /// <summary>
        /// Add the "first" page xml node to the xml document
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddFirstCellXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            if (GetFirstPage() > 1)
            {
                string url = GetRequestURL(navParameter, "0");
                AddPageXmlNode(parentNode, doc, FirstPageTextLabel, url, false, false, false, true);
            }
        }

        /// <summary>
        /// Add a page after the currently selected node
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddPageAfterCurrentXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            int end = GetLastPage();
            for (int i = _currentPage + 1; i <= end; i++)
            {
                string url = GetRequestURL(navParameter,
                                           ((i - 1) * _hitsPerPage).ToString());
                AddPageXmlNode(parentNode, doc, i.ToString(), url);
            }
        }

        /// <summary>
        /// Add a page before the currently selected node
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddPageBeforeCurrentXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            int start = GetFirstPage();
            for (int i = start; i < _currentPage; i++)
            {
                string url = GetRequestURL(navParameter,
                                           ((i - 1) * _hitsPerPage).ToString());

                AddPageXmlNode(parentNode, doc, i.ToString(), url);
            }
        }

        /// <summary>
        /// Add the currently selected page node to the xml document
        /// </summary>
        /// <param name="parentNode">Parent xml node where the current page shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        internal void AddCurrentXmlNode(XmlNode parentNode, XmlDocument doc, NavigationHelperParameter navParameter)
        {
            string url = GetRequestURL(navParameter,
                                           ((_currentPage - 1) * _hitsPerPage).ToString());

            AddPageXmlNode(parentNode, doc, _currentPage.ToString(), url, true, false, false);
        }

        /// <summary>
        /// Get the number of the first page to render a link to.
        /// </summary>
        /// <returns>the first page to show</returns>
        internal int GetFirstPage()
        {
            int start = 1;
            if (_currentPage - MaxNumberPageLinksBeforeCurrent > 0)
            {
                start = _currentPage - MaxNumberPageLinksBeforeCurrent;
            }
            return start;
        }

        /// <summary>
        /// Get the number of the last page to render a link to.
        /// </summary>
        /// <returns>the last page to show</returns>
        internal int GetLastPage()
        {
            int end = _currentPage;
            if (_totalPageNum > _currentPage)
            {
                if (_currentPage + MaxNumberPageLinksAfterCurrent > _totalPageNum)
                {
                    end = _totalPageNum;
                }
                else
                {
                    end = _currentPage + MaxNumberPageLinksAfterCurrent;
                }
            }
            return end;
        }

        /// <summary>
        /// Return the page naviation link with the offset (start1) parameter set to the osset parameter.
        /// It will also take the SearchGroup into consideration when the link is build.
        /// </summary>
        /// <param name="navParameter"><see cref="NavigationHelperParameter"/> used to build up the page link</param>
        /// <param name="offset">offset of the search for a page</param>
        /// <returns>URL string to be used for the page nodes</returns>
        internal string GetRequestURL(NavigationHelperParameter navParameter, string offset)
        {
            return NavigationHelper.SetParameter(navParameter, "start1", offset, ESPSearchGroupID);
        }        

        /// <summary>
        /// Add an XML Node (page) to the xml document
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="text">Text within the xml node</param>
        /// <param name="url">URL to be used for the page</param>
        internal static void AddPageXmlNode(XmlNode parentNode, XmlDocument doc, string text, string url)
        {
            AddPageXmlNode(parentNode, doc, text, url, false, false, false);
        }

        /// <summary>
        /// Add an XML Node (page) to the xml document.
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="text">Text within the xml node</param>
        /// <param name="url">URL to be used for the page</param>
        /// <param name="isSelected">Boolean specifying if this is the current node</param>
        /// <param name="isPrevNode">Boolean specifying if this is the prev node</param>
        /// <param name="isNextNode">Boolean specifying if this is the next node</param>
        internal static void AddPageXmlNode(XmlNode parentNode, XmlDocument doc, string text, string url, bool isSelected, bool isPrevNode, bool isNextNode)
        {
            AddPageXmlNode(parentNode, doc, text, url, isSelected, isPrevNode, isNextNode, false);
        }

        /// <summary>
        /// Add an XML Node (page) to the xml document.
        /// </summary>
        /// <param name="parentNode">Parent xml node where the page node shall be added</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="text">Text within the xml node</param>
        /// <param name="url">URL to be used for the page</param>
        /// <param name="isSelected">Boolean specifying if this is the current node</param>
        /// <param name="isPrevNode">Boolean specifying if this is the prev node</param>
        /// <param name="isNextNode">Boolean specifying if this is the next node</param>
        /// <param name="isFirstPageNode">Boolean specifying if this is the first page node</param>
        internal static void AddPageXmlNode(XmlNode parentNode, XmlDocument doc, string text, string url, bool isSelected, bool isPrevNode, bool isNextNode, bool isFirstPageNode)
        {
            XmlElement node = doc.CreateElement("Page");

            AddAttribute(node, doc, "isCurrent", isSelected.ToString().ToLower());
            AddAttribute(node, doc, "isNextPage", isNextNode.ToString().ToLower());
            AddAttribute(node, doc, "isPrevPage", isPrevNode.ToString().ToLower());
            AddAttribute(node, doc, "isFirstPage", isFirstPageNode.ToString().ToLower());
            
            XmlElement textNode = doc.CreateElement("Text");
            AddCDATASection(textNode, doc, text);

            XmlElement urlNode = doc.CreateElement("Url");
            AddCDATASection(urlNode, doc, url);

            node.AppendChild(textNode);
            node.AppendChild(urlNode);
            parentNode.AppendChild(node);
        }

        /// <summary>
        /// Add an Xml Attribute to an XmlNode
        /// </summary>
        /// <param name="parentNode">Node which shall contain the attribute</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="name">Name of the attribute</param>
        /// <param name="value">Value of the attribute</param>
        private static void AddAttribute(XmlNode parentNode, XmlDocument doc, string name, string value)
        {
            XmlAttribute attr = doc.CreateAttribute(name);
            attr.Value = value;
            parentNode.Attributes.Append(attr);
        }

        /// <summary>
        /// Add a CData section with the given content to an xml node
        /// </summary>
        /// <param name="parentNode">Node which shall contain the CDATA section</param>
        /// <param name="doc">XMLDocument to create an XMLNode</param>
        /// <param name="data">Data String within the CDATA section</param>
        private static void AddCDATASection(XmlNode parentNode, XmlDocument doc, string data)
        {
            XmlCDataSection cData = doc.CreateCDataSection(data);
            parentNode.AppendChild(cData);
        }

        #endregion
    }
}
